<?php

defined('__MAPEXEC__') or die('No direct access allowed');

/**
 * Exports a map as a Google map.
 */
class Maps_Export_Google extends Maps_Export_Html {
  
  /**
   * Class constructor
   * @param   string  Optional Google maps API key
   * @return  void
   */
  public function __construct($apiKey='') {
    parent::__construct($apiKey);
    $this->setAPIURL('http://maps.google.com/maps?file=api&sensor=false&v=%s&key=%s');
    $this->setAPIVersion('2.x');
    $this->apiKeyRequired = true;
  }
  
  /**
   * Gets the map layer control type.
   * @param   string  $type The map layer control type
   * @return  string  Google Maps type
   */
  protected function getLayerControlType($type='default') {
    $controlType = '';
  	switch ($type)
    {
  		case 'hybrid':
        $controlType = 'google.maps.HYBRID_MAP';
        break;
      case 'satellite':
        $controlType = 'google.maps.SATELLITE_MAP';
        break;
      case 'typography':
        $controlType = 'google.maps.PHYSICAL_MAP';
        break;
      default:
        $controlType = 'google.maps.NORMAL_MAP';
        break;
  	}
    return $controlType;
  }
  
  /**
   * Gets the max width of the POI info window.
   * @param   void
   * @return  integer   Maximum width in pixels
   */
  protected function getMaxInfoWindowWidth() {
    return round($this->getWidth() - 125);
  }
  
  /**
   * Gets the Google maps navigation code.
   * @param   string  $type Type of map navigation
   * @return  string  Google Maps navigation code
   */
  protected function getNavigationType($type='default') {
    $navType = '';
  	switch ($type)
    {
      case 'large';
        $navType = 'google.maps.LargeMapControl3D';
        break;
      case 'small';
        $navType = 'google.maps.SmallZoomControl3D';
        break;
      case 'inset';
        $navType = 'google.maps.OverviewMapControl';
        break;
      case 'scale':
        $navType = 'google.maps.ScaleControl';
        break;
      case 'small';
        $navType = 'google.maps.SmallMapControl';
        break;
    }
    return $navType;
  }
  
  /**
   * Renders the API version for this map.
   * @param   void
   * @return  string  Version string
   */
  protected function renderAPIVersion() {
    $version = '';
    
  	if ( is_string($this->apiVersion) ) {
      $version = explode('.', $this->apiVersion);
      $version = $version[0];
    } else {
      $version = $this->apiVersion > 1 ? floor($this->apiVersion) : '';
    }
    
    return $version;
  }
  
  /**
   * Renders the HTML and JavaScript code for Point Of Interest icons
   * @param   Maps_Icon_Manager_Common   Icon manager object
   * @return  string              HTML and JavaScript code
   */
  protected function renderIconCode(Maps_Icon_Manager_Common $iconManager) {
    $output = '';
    $mozPrintImage = '';
    $printImage = '';
    $shadowImage = '';
    
    if ( $iconManager->hasItems() ) {
      $output .=  $this->writeLn('Maps.Icons.Manager.Factory.create("google", function() {') .
                    $this->indentLn('var icon;') .
                    $this->writeLn();
      
      while ($icon = $iconManager->next()) {
        $output .=  $this->writeLn('icon = new google.maps.Icon(null, "'.$icon->getIconImageURL().'")') .
                    $this->writeLn('icon.iconSize = new google.maps.Size('.$icon->getIconWidth().','.$icon->getIconHeight().');');
        
        $mozPrintImage = $icon->getMozPrintImage();
        if ( !empty( $mozPrintImage ) ) {
          $output .= $this->writeLn('icon.mozPrintImage = "'.$icon->getMozPrintImageURL().'";');
        }
        
        $printImage = $icon->getPrintImage();
        if ( !empty( $printImage ) ) {
          $output .= $this->writeLn('icon.printImage = "'.$icon->getPrintImageURL().'";');
        }
        
        $shadowImage = $icon->getShadowImage();
        if ( !empty( $shadowImage ) ) {
          $output .=  $this->writeLn('icon.shadow = "'.$icon->getShadowImageURL().'";') .
                      $this->writeLn('icon.shadowSize = new google.maps.Size('.$icon->getShadowWidth().','.$icon->getShadowHeight().');');
        }
        
        $output .=  $this->writeLn('icon.iconAnchor = new google.maps.Point('.$icon->getAnchorOffsetX().','.$icon->getAnchorOffsetY().');') .
                    $this->writeLn('icon.infoWindowAnchor = new google.maps.Point('.$icon->getInfoWindowOffsetX().','.$icon->getInfoWindowOffsetY().');') .
                    $this->writeLn('this.add("'.$icon->getId().'", icon);') .
                    $this->writeLn();
      }
      
      $output .=  $this->outdentLn('});');
    }
    
    return $output;
  }
  
  /**
   * Renders the info window overlay HTML and JavaScript code
   * @param   Maps_Map_Common   Map object
   * @return  String            JavaScript code to add info windows to the map
   */
  private function renderInfoWindowOpen(Maps_Map_Common $map) {
    $str = '';
    if ( $map->directionsEnabled() ) {
      $str .= $this->writeLn('var infoWindowOpen = function(i) {') .
                $this->indentLn('var mapManager = Maps.Manager.Factory.get("google");') .
                $this->writeLn('var html = mapManager.createDirectionsToForm("'.$map->getId().'", "'.$map->lang->get('dir_form_head_to').'", "'.$map->lang->get('dir_form_label_start').'", "'.$map->lang->get('dir_form_button').'", poiLocs[i]) +') .
                  $this->indentLn('mapManager.createDirectionsFromForm("'.$map->getId().'", "'.$map->lang->get('dir_form_head_from').'", "'.$map->lang->get('dir_form_label_end').'", "'.$map->lang->get('dir_form_button').'", poiLocs[i])') .
                $this->outdentLn('var tabs = [') .
                  $this->indentLn('new google.maps.InfoWindowTab("'.$map->lang->get('tab_about').'", poiInfo[i]),') .
                  $this->writeLn('new google.maps.InfoWindowTab("'.$map->lang->get('tab_directions').'", html)') .
                $this->outdentLn('];') .
                $this->writeLn('pois[i].openInfoWindowTabsHtml(tabs, {maxWidth:'.$this->getMaxInfoWindowWidth().'});') .
                $this->writeLn('mapManager = null;') .
              $this->outdentLn('};');
    } else {
      $str .= $this->writeLn('var infoWindowOpen = function(i) {') .
                $this->indentLn('pois[i].openInfoWindowHtml(poiInfo[i], {maxWidth:'.$this->getMaxInfoWindowWidth().'});') .
              $this->outdentLn('};');
    }
    
    return $str;
  }
  
  /**
   * Renders the HTML and JavaScript to make the map appear.
   * @param   Maps_Map_Common   Map object
   * @return  Atring            HTML and JavaScript code
   */
  protected function renderInvocationCode(Maps_Map_Common $map) {
  	$str =  $this->writeLn( $this->openScript(null, true) ) .
              $this->indentLn('var mapManager = Maps.Manager.Factory.get("google");') .
              $this->writeLn() .
              $this->writeLn('mapManager.add(function() {') .
                $this->indentLn() .
                $this->renderInvocationVariables($map) .
                $this->writeLn() .
                $this->writeLn('latlng = new google.maps.LatLng('.$map->getCenterLat().','.$map->getCenterLng().');') .
                $this->writeLn('map.setCenter(latlng);') .
                $this->writeLn('map.setZoom(1);') .
                $this->writeLn() .
                $this->renderMapNavigation( $map->getNavigationControls() ) .
                $this->writeLn() .
                $this->renderMapLayerControls( $map->getLayerControls() ) .
                $this->writeLn() .
                $this->renderMapPolygons($map) .
                $this->writeLn() .
                $this->renderMapLines($map) .
                $this->writeLn() .
                $this->renderMapPOIs($map);
    if ( $map->getPOICount() == 0 || !$map->optimalZoomEnabled() ) {
      $str .=   $this->writeLn('map.setZoom('.$map->getZoomLevel().');');
    }
    $str .=     $this->writeLn('map.savePosition();') .
                $this->writeLn() .
                $this->writeLn('// Return newly created map object') .
                $this->writeLn('return map;') .
              $this->outdentLn('});') .
            $this->outdentLn( $this->closeScript(true) );
    return $str;
  }
  
  /**
   * Renders the JavaScript variable declarations for all variables used
   * in the invocation code.
   * @param   Maps_Map_Common   Map object
   * @return  String            JavaScript variable declarations
   */
  protected function renderInvocationVariables(Maps_Map_Common $map) {
    $str =  $this->writeln('var map = new google.maps.Map'.$this->renderAPIVersion().'( document.getElementById("'.$map->getId().'") );') .
            $this->writeLn('var pois = [];') .
            $this->writeLn('var latlng = null;') .
            $this->writeLn('var line = null;') .
            $this->writeLn('var poiInfo = {};') .
            $this->writeLn('var poiLocs = {};') .
            $this->writeLn('var polygon = null;') .
            $this->writeLn('var iconManager = Maps.Icons.Manager.Factory.get("google");') .
            $this->writeLn('var sidebar = document.getElementById("'.$map->getId().'_sidebar");') .
            $this->writeLn('var sidebarLinks = null;') .
            $this->writeLn('var s = 0;') .
            $this->writeLn('var sLen = 0') .
            $this->writeLn() .
            $this->renderInfoWindowOpen($map) .
            $this->writeLn() .
            $this->writeLn('var sidebarClickHandler = function(i) {') .
              $this->indentLn('return function() {') .
                $this->indentLn('infoWindowOpen(i);') .
                $this->writeLn('return false;') .
              $this->outdentLn('};') .
            $this->outdentLn('};') .
            $this->writeLn() .
            $this->writeLn('var poiClickHandler = function(i) {') .
              $this->indentLn('return function() { infoWindowOpen(i); };') .
            $this->outdentLn('};');
    
    if ( $map->optimalZoomEnabled() ) {
      $str .= $this->writeLn() .
              $this->writeLn('var bds = new google.maps.LatLngBounds();');
    }
    
    return $str;
  }
  
  /**
   * Renders the map layer controls.
   * @param   array   $layerControls Array of layer control types.
   * @return  string  JavaScript for layer controls.
   */
  private function renderMapLayerControls(array $layerControls) {
    $output = '';

    if ( empty($layerControls) ) {
    	return $output;
    }

    foreach ($layerControls as $type) {
    	$output .= $this->writeLn('map.addMapType('.$this->getLayerControlType($type).');');
    }
    
    $output .= $this->writeLn('map.addControl( new google.maps.MapTypeControl() );');

    return $output;
  }
  
  /**
   * Renders the map lines.
   * @param   Maps_Map_Common   Map object
   * @return  String            JavaScript map line code
   */
  private function renderMapLines(Maps_Map_Common $map) {
    $line = null;
    $latlng = null;
    $output = '';
    
    if ($map->lines->getCount() < 1) {
      return $output;
    }
    
    while ($line = $map->lines->next()) {
      $output .= $this->write('line = new google.maps.Polyline([');
      if ($line->getCount() > 1) {
        while ($latlng = $line->next()) {
          if ($line->getPointer() > 1) {
            $output .= ', ';
          }
          $output .= 'new google.maps.LatLng('.$latlng.')';
        }
        $output .=  '], "'.$line->getStrokeColor().'", "'.$line->getStrokeWeight().'");' .
                    $this->writeLn() .
                    $this->writeLn('map.addOverlay(line);');
      }
    }
    
    return $output;
  }
  
  /**
   * Renders the code for map Points Of Interest (markers).
   * @param   Maps_Map_Common   Map object
   * @return  String            JavaScript code to add map Points of Interest
   */
  private function renderMapPOIs(Maps_Map_Common $map) {
    $index = 0;
    $output = '';
    $poi = null;
    $title = '';
    $info = '';
    $lat = null;
    $lng = null;
    $mapId = $map->getId();
    
    if ( $map->optimalZoomEnabled() ) {
      $output .=  $this->writeLn('bds.extend(latlng);') .
                  $this->writeLn();
    }
    
    while ($poi = $map->pois->next()) {
      $lat = $poi->getLat();
      $lng = $poi->getLng();
      
      if ( isset($lat) && isset($lng) ) {
        $info = $this->renderPoiInfo($poi, $map);
        $name = $this->escape( $poi->getName() );
        $iconName = $poi->getIconName();
        
        $output .=  $this->writeLn('latlng = new google.maps.LatLng('.$lat.','.$lng.');');
        
        if ( $map->optimalZoomEnabled() ) {
          $output .= $this->writeLn('bds.extend(latlng);');
        }
        
        if ( empty($iconName) ) {
          $output .= $this->writeLn('pois['.$index.'] = new google.maps.Marker(latlng, {title:"'.$name.'"});');
        } else {
          $output .= $this->writeLn('pois['.$index.'] = new google.maps.Marker(latlng, {title:"'.$name.'", icon:iconManager.get("'.$iconName.'") });');
        }
        
        $output .=  $this->writeLn('poiInfo['.$index.'] = "'.$this->escape($info).'";') .
                    $this->writeLn('poiLocs['.$index.'] = "'.$this->renderPoiLocation($poi).'";') .
                    $this->writeLn('google.maps.Event.addListener(pois['.$index.'], "click", poiClickHandler('.$index.'));') .
                    $this->writeLn('map.addOverlay(pois['.$index.']);') .
                    $this->writeLn();
      }
      
      $index++;
    }
    
    if ( $map->optimalZoomEnabled() ) {
      $output .=  $this->writeLn('map.setZoom( map.getBoundsZoomLevel(bds) );') .
                  $this->writeLn('map.setCenter( bds.getCenter() );') .
                  $this->writeLn();
    }
    
    $output .=  $this->writeLn('if (sidebar) {') .
                  $this->indentLn('sidebarLinks = sidebar.getElementsByTagName("a");') .
                  $this->writeLn('s = 0;') .
                  $this->writeLn('sLen = sidebarLinks.length;') .
                  $this->writeLn('while (s < sLen) {') .
                    $this->indentLn('sidebarLinks[s].onclick = sidebarClickHandler(s);') .
                    $this->writeLn('s++;') .
                  $this->outdentLn('}') .
                  $this->writeLn('sidebarLinks = null;') .
                  $this->writeLn('sidebar = null;') .
                $this->outdentLn('}') .
                $this->writeLn() .
                $this->writeLn('iconManager = null;');

    return $output;
  }
  
  /**
   * Renders the map polygon code
   * @param   Maps_Map_Common   Map object
   * @return  String            JavaScript code for map polgons
   */
  private function renderMapPolygons(Maps_Map_Common $map) {
    $latlng = null;
    $output = '';
    $polygon = null;
    
    if ($map->polygons->getCount() < 1) {
      return $output;
    }
    
    while ($polygon = $map->polygons->next()) {
      if ( $polygon->isClosed() ) {
        $output .= $this->write('polygon = new google.maps.Polygon([');
        
        while ($latlng = $polygon->next()) {
          if ($polygon->getPointer() > 1) {
            $output .= ', ';
          }
          $output .= 'new google.maps.LatLng('.$latlng.')';
        }
        
        $output .= '], "'.$polygon->getStrokeColor().'", '.$polygon->getStrokeWeight().', '.$polygon->getStrokeOpacity().', "'.$polygon->getFillColor().'", '.$polygon->getFillOpacity().');' .
                    $this->writeLn() .
                    $this->writeLn('map.addOverlay(polygon);');
      }
    }
    
    return $output;
  }
  
  /**
   * Renders the map navigational elements.
   * @param   array   $navControls List of map navigation control types.
   * @return  string  JavaScript for map navigation controls.
   */
  private function renderMapNavigation(array $navControls) {
    $controlType = '';
    $output = '';

    if (empty($navControls)) {
      return $output;
    }

    foreach ($navControls as $control) {
      $controlType = $this->getNavigationType($control);
      if ( !empty($controlType) ) {
        $output .= $this->writeLn('map.addControl( new '.$controlType.'() );');
      }
    }

    return $output;
  }
  
  protected function renderPoiLinks(Maps_Map_POI $poi, Maps_Map_Common $map) {
    return '';
  }
  
  /**
   * Renders the location of a Point Of Interest
   * @param   Maps_Map_POI  Point Of Interest object
   * @return  String        Latitude and longitude info of POI
   */
  protected function renderPoiLocation(Maps_Map_POI $poi) {
    return $poi->getLat() . ',' . $poi->getLng() . ' (' . $this->escape( $poi->getName() ) . ')';
  }
  
}
?>